package service

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/zaper/private-logger"
	"gitee.com/zaper/service-center/util"
	"github.com/goccy/go-json"
	"go.etcd.io/etcd/client/v3"
	"strconv"
	"time"
)

type RegisterAgent struct {
	regAttr    *registerAttr
	clientEtcd *clientv3.Client
	lease      clientv3.Lease
}

func NewRegisterAgent(clientEtcd *clientv3.Client, option ...Option) *RegisterAgent {
	regAttr := &registerAttr{
		// 给一组初始值
		ctx:       context.Background(),
		nameSpace: "/microservices",
		ttl:       30,
		maxRetry:  3,
	}
	for _, o := range option {
		o(regAttr)
	}
	return &RegisterAgent{
		regAttr:    regAttr,
		clientEtcd: clientEtcd,
	}
}

type Option func(*registerAttr)

type registerAttr struct {
	ctx       context.Context
	nameSpace string
	ttl       int64
	maxRetry  int
}

func WithContext(ctx context.Context) Option {
	return func(o *registerAttr) {
		o.ctx = ctx
	}
}

func WithNameSpace(nameSpace string) Option {
	return func(o *registerAttr) {
		o.nameSpace = nameSpace
	}
}

func WithTTl(ttl int64) Option {
	return func(o *registerAttr) {
		o.ttl = ttl
	}
}

func WithMaxRetry(maxRetry int) Option {
	return func(o *registerAttr) {
		o.maxRetry = maxRetry
	}
}

func (r *RegisterAgent) RegisterRoute(requestService *InstanceInfo) int {
	if requestService == nil {
		return RegisterErr
	}
	leaseID := parseLeaseID(requestService)
	if leaseID == -1 {
		// 租约id在元数据存在，如果客户端丢失了租约id即后续无法续租，则走注册重新获取租约
		return Register
	}
	serviceKey := r.getServiceKey(requestService)
	resp, err := r.clientEtcd.Get(r.regAttr.ctx, serviceKey)
	if err != nil {
		logger.Errorf("register fail")
		// 默认走注册覆盖
		return Register
	}
	instances := r.getServiceValueInstance(resp)
	for _, instance := range instances {
		// 只有实例已存在，且客户端明确带有租约id时 走续约
		if requestService.ID == instance.ID {
			return KeepLive
		}
	}
	// 示例不存在走注册
	return Register
}

func (r *RegisterAgent) ServiceRegister(requestService *InstanceInfo) (clientv3.LeaseID, error) {
	logger.Infof("Register start, request is %s", requestService)
	// 当前先不考虑元数据，只是用于入参区分注册还是续租
	requestService.Metadata = make(map[string]string)
	requestService.Metadata["timeStr"] = util.GetCurrentTime()
	serviceKey := r.getServiceKey(requestService)
	serviceValue := r.getServiceValueString(requestService)
	if r.lease != nil {
		err := r.lease.Close()
		if err != nil {
			logger.Errorf("close old lease fail")
			return -1, err
		}
	}
	r.lease = clientv3.NewLease(r.clientEtcd)
	grant, err := r.lease.Grant(r.regAttr.ctx, r.regAttr.ttl)
	_, err = r.clientEtcd.Put(r.regAttr.ctx, serviceKey, serviceValue, clientv3.WithLease(grant.ID))
	if err != nil {
		logger.Errorf("Register service fail, err is %s", err)
		return -1, err
	}
	logger.Infof("Register success, grantId is %d", grant.ID)
	return grant.ID, nil
}

func (r *RegisterAgent) KeepAlive(requestService *InstanceInfo) (clientv3.LeaseID, error) {
	logger.Infof("KeepAlive start, request is %s", requestService)
	leaseID := parseLeaseID(requestService)
	for i := 0; i < r.regAttr.maxRetry; i++ {
		resp, err := r.clientEtcd.KeepAliveOnce(r.regAttr.ctx, leaseID)
		if err == nil {
			logger.Infof("KeepAlive success, grantId is %d", resp.ID)
			return resp.ID, nil
		}
		time.Sleep(100 * time.Millisecond)
		logger.Errorf("keep alive fail, err is %s", err)
	}
	// 如果续租失败，则直接注册兜底
	leaseID, err := r.ServiceRegister(requestService)
	if err == nil {
		return leaseID, nil
	}
	return -1, errors.New("keep alive fail")
}

func getLeaseId(clientEtcd *clientv3.Client) clientv3.LeaseID {
	lease, err := clientEtcd.Grant(context.Background(), 30)
	if err != nil {
		logger.Errorf("Grant is fail, %s", err)
		return 0
	}

	return lease.ID
}

func (r *RegisterAgent) getServiceKey(requestService *InstanceInfo) string {
	return fmt.Sprintf("/%s/%s/%s", r.regAttr.nameSpace, requestService.Name, requestService.ID)
}

func (r *RegisterAgent) getServiceValueString(service *InstanceInfo) string {
	value, err := json.Marshal(service)
	if err != nil {
		return ""
	}
	return string(value)
}

func (r *RegisterAgent) getServiceValueInstance(response *clientv3.GetResponse) []InstanceInfo {
	var instances []InstanceInfo
	serviceInstance := InstanceInfo{}
	if response.Kvs == nil {
		return nil
	}
	for _, kv := range response.Kvs {
		err := json.Unmarshal(kv.Value, &serviceInstance)
		if err != nil {
			continue
		}
		instances = append(instances, serviceInstance)
	}
	return instances
}

func parseLeaseID(requestService *InstanceInfo) clientv3.LeaseID {
	if requestService == nil || requestService.Metadata == nil {
		return -1
	}
	leaseId, ok := requestService.Metadata[leaseIdKey]
	if !ok {
		return -1
	}
	leaseID, err := strconv.ParseInt(leaseId, 10, 64)
	if err != nil {
		return -1
	}
	return clientv3.LeaseID(leaseID)
}
